import pexpect
import sys, Queue, threading
import time
import re

import addrutil, dbmgr, getliveip

# update: 2011.5.1 14:29 1304231453    two thread for a tr-server

class ProbeTargetThread(threading.Thread):
  def __init__ (self, server_addr, liveip, pfx, username, password, ping_type, srv_id, db):
    threading.Thread.__init__(self)
    self.server = server_addr
    self.liveip = liveip 
    self.pfx = pfx
    self.username = username
    self.password = password
    self.type = ping_type
    self.srv_id = srv_id
    self.db = db
    self.deadline = pfx.bgptime + 120

  def run(self):
    if self.liveip:
      self.setName(self.server + " > " + self.liveip)
    else:
      self.setName(self.server + " > " + self.pfx.dec)
    child = pexpect.spawn('telnet %s' % self.server)
    # child.logfile_read = sys.stdout #
    if len(self.username) != 0:
      uidx = child.expect(['(?i)login:', '(?i)username:', '(?i)user:',
        pexpect.TIMEOUT, pexpect.EOF], 30)
      if uidx <= 2:
        child.sendline(self.username)
        # print "<<<<< %s ////// %s >>>>>" % (child.before, child.after)
      else:
        addrutil.log(int(time.time()), 0, "USERNAME Wrong: %s!" % self.server)
        #child.sendcontrol('c')
        child.close(True)
        return
    if len(self.password) != 0:
      pidx = child.expect(['\r\n(?i)password:',
        pexpect.TIMEOUT, pexpect.EOF], 30)
      if pidx == 0:
        child.sendline(self.password)
        # print "<<<<< %s ////// %s >>>>>" % (child.before, child.after)
      else:
        addrutil.log(int(time.time()), 0, "PASSWORD Wrong: %s!" % self.server)
        #child.sendcontrol('c')
        child.close(True)
        return
    while True:
      child.sendline("")
      ptidx = child.expect(['[>#$] *\r\n', '\r\n(?i)password:',
        pexpect.TIMEOUT, pexpect.EOF], 30)
      if ptidx > 1:
        # print "<<<<< %s ////// %s >>>>>" % (child.before, child.after)
        addrutil.log(int(time.time()), 0,
            'PROMPT Wrong: %s! [[[%s]]]' % (self.server, child.before))
        #child.sendcontrol('c')
        child.close(True)
        return
      elif ptidx == 1:
        child.sendline(self.password)
      else:
        break
      if int(time.time()) + 30 > self.deadline:
        #child.sendcontrol('c')
        child.close(True)
        return
    # show bgp
    if self.liveip is None:
      prev_is_good_path = -1
      netheader = self.pfx.dec.split('.')[0]
      while True:
        end = int(time.time())
        if end > self.deadline:
          break
        # get home AS
        child.sendline('')
        child.sendline("%s%s" % (BGPCommand[self.type + 1], self.pfx.dec))
        bgptime = int(time.time())
        for ib in xrange(0, 12): child.send(' ') # '^ --MORE--$'
        uidx = child.expect([
          '\r\n.+%s%s' % (BGPCommand[self.type + 1], netheader),
          pexpect.TIMEOUT, pexpect.EOF], 5)
        promt = ''
        if uidx == 0:
          for ipt in xrange(len(child.after) - 1, -1, -1):
            if child.after[ipt] == '\r' or child.after[ipt] == '\n':
              promt = child.after[ipt + 1 : len(child.after)
                  - len(BGPCommand[self.type + 1]) - len(netheader)]
              break
          uidx = child.expect(['\r\n.+\r\n%s' % promt,
            pexpect.TIMEOUT, pexpect.EOF], 5)
        re.sub('\.', '\\\.', promt)
        bestrt = ''
        #bgphome = 0
        ibst = 0
        if uidx == 0:
          if self.type <= 0:
            reslines = child.after.split('\r\n')
            for rline in reslines:
              if rline.startswith('Paths: ('):
                firstline = rline.split(' ')
                for ifl in xrange(0, len(firstline)):
                  if firstline[ifl] == 'best':
                    sbst = firstline[ifl + 1][1 : len(firstline[ifl + 1]) - 1]
                    if re.search('^[1-9]+$', sbst): ibst = int(sbst)
                    break
                break
            for irl in xrange(0, len(reslines)):
              if re.search('^  [\(1-9]', reslines[irl]) or (
                  reslines[irl].startswith(' --More--') and
                  irl + 1 < len(reslines) and
                  re.search('^  [^ ]', reslines[irl + 1]) ):
                ibst = ibst - 1
                if ibst > 0:
                  continue
                bestrt = re.sub(', \(.+\)', '', reslines[irl])
                bestrt = re.sub('\(.+\)', '', bestrt)
                bestrt = re.sub('\{.+\}', '', bestrt).strip()
                #uptpath = bestrt.split(' ')
                #bgphome = addrutil.asn2int(uptpath[len(uptpath) - 1])
                break
          else:
            reslines = child.after.split('\r\n')
            for irl in xrange(0, len(reslines)):
              if re.search(' \*\[BGP', reslines[irl]):
                bestrt = re.sub(', \(.+\)', '', reslines[irl + 1])
                bestrt = re.sub('\(.+\)', '', bestrt)
                bestrt = re.sub('\{.+\}', '', bestrt)
                bestrt = re.sub('AS path: ', '', bestrt)
                bestrt = re.sub('( I)|( E)|( \?)$', '', bestrt).strip()
                #uptpath = bestrt.split(' ')
                #bgphome = addrutil.asn2int(uptpath[len(uptpath) - 1])
                break
        #if bgphome == self.newhome: isoldhome = 0
        #else: isoldhome = 1
        if re.search(self.pfx.bad_path_re, bestrt):
          is_good_path = 0 
        else:
          is_good_path = 1
        if prev_is_good_path != is_good_path:
          self.db.dbQueue.put(("route", (self.pfx.alarm_id, bgptime,
            self.srv_id, is_good_path, self.pfx.bgptime)))
          prev_is_good_path = is_good_path 
        time.sleep(1)
    else: # ping liveip
      child.sendline('')
      presp = -1
      ptime = -1
      #addrutil.log(int(time.time()), 0, 'INFO: ping %s' % self.liveip) #
      if self.type <= 0: # -1, 0
        while True:
          child.sendline("ping %s" % self.liveip)
          beg = int(time.time())
          gidx = child.expect(['[!\.]{5}', pexpect.TIMEOUT, pexpect.EOF], 10)
          end = int(time.time())
          delta = (end - beg) / 5.0;
          if gidx == 0:
            resp_str = child.after
          else:
            resp_str = "....."
          for it in xrange(0,5):
            if resp_str[it] == ".":
              resp = 0
            else:
              resp = 1
            ctime = int(beg + it * delta)
            if (presp != resp) and (ptime != ctime):
              self.db.dbQueue.put(("probe", (self.pfx.alarm_id, ctime,
                self.srv_id, resp, self.pfx.bgptime)))
              presp = resp
              ptime = ctime
          if end > self.deadline:
            break
      else: # 1, 2, 3, 4
        child.sendline("ping %s" % self.liveip)
        while True:
          ctime = int(time.time())
          gidx = child.expect(['time=', pexpect.TIMEOUT, pexpect.EOF], 2)
          if gidx == 0:
            resp = 1
          else:
            resp = 0
          if (presp != resp) and (ptime != ctime):
            self.db.dbQueue.put(("probe", (self.pfx.alarm_id, ctime,
              self.srv_id, resp, self.pfx.bgptime)))
            presp = resp
            ptime = ctime
          if ctime > self.deadline:
            break
        child.sendcontrol('c')
    child.sendline("exit")
    child.close(True)

BGPCommand = [
  "show bgp ipv4 unicast ",
  "show ip bgp ",
  "show route exact active-path ",
  "show route active-path ",
  "show route exact ",
  "show route "
]

RouteServers = [
#from http://www.traceroute.org/
  ("route-server.belwue.de", "", "", 553, 0), # 193.196.190.135
  ("route-server.rogers.com", "", "", 812, 0), # 64.71.255.61 # !exit to quit
  ("route-views.on.bb.telus.com", "", "", 852, 0), # 154.11.63.86
  ("route-views.ab.bb.telus.com", "", "", 852, 0), # 154.11.98.18
  ("route-server.cerf.net", "", "", 17233, 0), # 12.129.193.235 # (whois)17233 (!web)1838
   #("route-server.tenet.ac.za", "", "rviews", 2018, 0), # 155.232.137.250 # !no ping
  ("route-server.ip.tiscali.net", "", "", 3257, 0), # 213.200.64.94
  ("route-server.ip.tdc.net", "rviews", "Rviews", 2109, 1), # 192.66.4.239 # (whois) 2109 (!web)3292, 2109 ->(c2p) 3292
  ("route-server.ip-plus.net", "", "", 3303, 0), # 164.128.251.50
  ("route-server.gblx.net", "", "", 3549, 0), # 67.17.81.28
  ("route-server.eu.gblx.net", "", "", 3549, 0), # 67.17.81.187
   #("route-server.savvis.net", "", "", 3561, 0), # 209.1.220.234 # !connection timeout
  ("public-route-server.is.co.za", "", "", 3741, 0), # 196.4.160.227
  ("route-server.twtelecom.net", "rviews", "rviews123", 4323, 1), # 66.195.95.174
   #("route-server.as5388.net", "", "", 5388, 0), # 195.92.201.108 # !connection timeout
  ("route-server.as5413.net", "", "", 5413, 0), # 212.241.174.1
   #("route-server.opentransit.net", "rviews", "Rviews", 5511, 1), # 204.59.3.38 # !no ping
  ("tpr-route-server.saix.net", "saix", "saix", 5713, 0), # 196.25.9.46
  ("route-server.videotron.net", "", "", 5769, 0), # 216.113.14.121
  ("route-server-east.gt.ca", "", "", 6539, 0), # 216.18.63.214
  ("route-server-west.gt.ca", "", "", 6539, 0), # 66.59.190.81
   #("route-server.skyinet.net", "", "", 6648, 0), # 203.115.134.70 # !connection timeout
  ("route-server.as6667.net", "rviews", "Rviews", 6667, 4), # 213.192.191.82 # !exit to quit
   #("routeserver.sunrise.ch", "", "", 6730, 0), # 193.192.254.90 # !ping no response !!!!
   #("route-server.astralnet.ro", "", "", 6746, 0), # 193.231.255.252 # !connection refused
   #("route-server.he.net", "", "", 6939, 0), # 64.62.142.154 # !no ping
  ("route-server.ip.att.net", "rviews", "", 7018, 0), # 12.0.1.28
  ("route-server.sbcglobal.net", "public", "public", 7132, 3), # 66.159.184.170
  ("route-views.optus.net.au", "", "", 7474, 0), # 203.202.125.6
   #("route-server.wcg.net", "", "", ?3356?, 0), # 64.200.246.66, (!web)7911, (whois)3356 # !no route to host
  ("route-server.newyork.ny.ibone.comcast.net", "rviews", "", 7922, 0), # 66.208.229.1 # !'show bgp' authorization failed !!!!
  ("route-server.as8218.eu", "rviews", "Rviews", 8218, 1), # 83.167.56.120
   #("route-server.colt.net", "", "", ?8220?, 0), # 212.74.64.138 # !connection timeout
  ("route-server.gibtelecom.net", "", "", 8301, 0), # 212.120.249.25
   #("route-views.bmcag.net", "", "", ?9132?, 0), # # !unknow host
   #("route-server-au.exodus.net", "", "", ?9328?, 0), # 64.15.33.9 # !no route to host
   #("route-server.mix.com.ph", "", "", ?9658?, 0), # 203.167.102.43 # (!web)9670, (whois)9658 # !connection timeout
  ("route-server.eastlink.ca", "", "", 11260, 0), # 24.137.100.8
   #("route-view.ip.nacamar.net", "", "", ?12312?, 0), # 62.144.208.134 # !connection timeout
   #("route-server.host.net", "", "routes", 13645, 0), # 75.119.191.74 # !no ping
  ("route-server.east.allstream.com", "", "", 15290, 0), # 216.191.14.250
  ("route-server.west.allstream.com", "", "", 15290, 0), # 209.82.88.118
   #("route-server.rhein-main-saar.net", "", "", 21740, 0), # # (!web)15837, (whois) 21740 # !name not know
   #("bgp-view.tvnetwork.hu", "", "", 21229, 0), # 80.95.64.12 # !no ping
   #("route-server.nevadanap.com", "rviews", "rviews", 23005, 0), # 66.209.84.9 # !connection timeout
   #("route-server.as28747.net", "", "", ?21844?, 0), # # (!web)28747, (whois)21844 # !name not know
   #("route-server.occaid.net", "", "", ?31985?, 0), # 66.39.192.82 # (!web)30071, (whois)31985 # !network unreachable
   #("route-server.ipilink.net", "", "", ?30890?, 0), # 86.55.0.50 # !connection timeout
  ("route-server.vpls.net", "rviews", "", 4213, 0), # 74.222.138.49 # also (c2p) AS35908
   #("route-server.centauricom.com:2605", "", "", ?19214?, 0), # 208.74.64.68 # (!web)35975, (whois)19214 # !connection refuse
  ("route-views.oregon-ix.net", "rviews", "", 3582, 0), # 128.223.51.103 # (!web)6447 (whois)3582
   #("route-server.utah.rep.net", "", "", 0, 0), # # !unknow host
   #("www.netlantis.org", "", "", 6893, 0), # 62.220.132.1 # !connection timeout
   #("lg.rsix.tche.br", "", "", ?2716?, 0), # 200.132.0.9 # !no ping
   #("lg.ptt.ansp.br", "", "", 3549, 0), # 200.136.34.251 # !connection timeout
   #("lg.sp.ptt.br", "", "", ?22548?, 0), # 200.160.1.3 # !no ping
#from http://www.bgp4.as/looking-glasses
  ("route-server.tinet.net", "", "", 3257, 0), # 213.200.64.94
  ("route-server.playnet.it", "rviews", "", 13284, 0), # 62.48.45.74
   #("grh.sixxs.net", "", "", 8298, 0), # 213.197.29.32 # !no ping
  ("routeserver.ginx.org.za", "bgp", "bgp", 2018, 0), # 192.42.99.200
#from http://www.bgp4.net/wiki/doku.php?id=tools:ipv4_route_servers
  ("route-server.nlayer.net", "public", "public", 4436, 1), # 69.31.111.162
  ("route-views.routeviews.org", "rviews", "", 3582, 0), # 128.223.51.103 # (!web)6447, (whois)3582
   #("route-views2.routeviews.org", "", "", 3582, 0), # 128.223.51.102 # (!web)6447, (whois)3582 # !no ping
  ("route-views3.routeviews.org", "rviews", "", 3582, 0), # 128.223.51.108 # (!web)6447, (whois)3582
   #("route-views.eqix.routeviews.org", "", "", 3582, 0), # 206.223.132.88 # (!web)6447, (whois)3582 # !no ping
   #("route-views.isc.routeviews.org", "", "", 3582, 0), # 204.152.184.212 # (!web)6447, (whois)3582 # !no ping
   #("route-views.kixp.routeviews.org", "", "", 3582, 0), # 80.240.194.138 # (!web)6447, (whois)3582 # !no ping
   #("route-views.linx.routeviews.org", "", "", 3582, 0), # 195.66.241.146 # (!web)6447, (whois)3582 # !no ping
   #("route-views.wide.routeviews.org", "", "", 3582, 0), # 203.178.141.138 # (!web)6447, (whois)3582 # !no ping
  ("route-server.gt.ca", "", "", 6539, 0), # 216.18.63.214
   #("route-server.bti.net.ph", "", "", 6648, 0), # 203.115.134.70 # !connection timeout
  ("routeserver.man-da.de", "guest", "", 8365, -1), # 82.195.67.41
   #("route-server.manilaix.net.ph", "", "", 9658, 0), # 203.167.102.43 # (!web)9670 (whois)9658 # !connection timeout
   #("route-server.central.allstream.com", "", "", 15290, 0), # 216.191.167.134 # !no ping
   #("route-server.mainz-kom.net", "", "", 8881, 0), # 82.145.0.162 # !connection timeout
  ("route-server.as25358.net", "rviews", "Rviews", 25358, 1) # 195.140.148.62
#from http://routeserver.org
#from http://www.netdigix.com/servers.html
   #("route-server.exodus.net", "", "", 3561, 0), # 209.1.220.234 # (!web)3967 (whois)3561 # !connection timeout
#from http://www.inetdaemon.com/tools/route_servers.shtml
   #("ner-routes.bbnplanet.net", "", "", 3356, 0), # 4.2.34.165 # !connection timeout
   #("route-server-ap.exodus.net", "", "", 0, 0), # # !unknow host
   #("route-server-eu.exodus.net", "", "", 0, 0) # # !unknow host
]

targetQueue = Queue.PriorityQueue()
def probeTargetRun():
  prevprob = 0
  while True:
    priority, pfx, inq_time, db = targetQueue.get(True)
    outQTime = int(time.time())
    if outQTime - pfx.bgptime > 60:
      continue
    if pfx.bgptime - prevprob < 150:
      continue
    prevprob = pfx.bgptime 
    liveip, islive = getliveip.getLiveIP(
        pfx.hole_ipbeg, pfx.hole_ipend, pfx.is_ipv6)
    if islive == 0 and pfx.bad_path_re.count(' ') == 2:
      # TODO reduce # of anomalies
      continue
    addrutil.log(outQTime, 0,
      "INFO: alarm %d out targetQueue. size %d. RIO(%d). B[%s]" % (
      pfx.alarm_id, targetQueue.qsize(), outQTime, pfx.bad_path_re))
    threads = []
    srv_id = 0
    for rs in RouteServers:
      threads.append(ProbeTargetThread(
          rs[0], None, pfx, rs[1], rs[2], rs[4], srv_id, db))
      threads.append(ProbeTargetThread(
          rs[0], liveip, pfx, rs[1], rs[2], rs[4], srv_id, db))
      srv_id += 1
    for t in threads:
      t.start()
    for t in threads:
      t.join(pfx.bgptime + 140 - int(time.time())) # TODO: wait 140 seconds
    db.dbQueue.put(("signature", (pfx.bgptime, pfx.alarm_id)))
    addrutil.log(int(time.time()), 0,
      "INFO: alarm %d out targetQueue. size %d. RIOE(%d). ip %s live %d" % (
      pfx.alarm_id, targetQueue.qsize(), int(time.time()), liveip, islive))

def probeTarget(pfx, db):
  inq_time = int(time.time())
  addrutil.log(inq_time, 0,
    "INFO: alarm %d in targetQueue. qsize: %d. RI(%d:%d)" % (
    pfx.alarm_id, targetQueue.qsize(), pfx.bgptime, inq_time))
  priority = pfx.bad_path_re.count(' ')
  targetQueue.put((priority, pfx, inq_time, db))

if __name__ == '__main__':
  outfile = sys.stdout
  errfile = sys.stderr
  threads = []
  for irs in xrange(0, len(RouteServers)):
    rs = RouteServers[irs]
    #threads.append(ProbeTargetThread(rs[0], '166.111.68.233', '166.111.0.0/16',
    #    rs[1], rs[2], rs[3], rs[4], outfile, errfile, int(time.tim()) + 120))
    #threads.append(ProbeTargetThread(rs[0], '59.66.214.110', '59.66.0.0/16',
    #    rs[1], rs[2], rs[3], rs[4], outfile, errfile, int(time.tim()) + 120))
  for t in threads: t.start()
  for t in threads: t.join()

